
/**
 * This file is part of iSecurePasswords.
 *
 *   iSecurePasswords is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   iSecurePasswords is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with iSecurePasswords.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Matthew MacGregor
 * Created: 2012
 * iSecurePasswords, copyright 2013 Sudolink, LLC
 * 
 */ package com.sudolink.cryptodata;

import com.sudolink.isp.app.Application ;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.sudolink.crypto.CryptoException;
import com.sudolink.io.IFetch;
import com.sudolink.io.IStore;
import com.sudolink.isp.enums.DatabaseVersion;
import com.sudolink.isp.enums.EncryptionVersion;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.digest.DigestUtils;

/**
 *
 * @author matsu
 */
public class SecureDataManager implements ISecureDataManager {

    private Data data;

    public SecureDataManager() {
        data = new Data();

    }

    public void reset() {
        data = new Data();
    }
    
    /**
     *
     * @param isd
     */
    public void addAll( SecureData[] isd ) {
        data.nodes.addAll(Arrays.asList(isd));
    }
    
    public void resetAll( SecureData[] isd ) {
        data.nodes.clear();
        data.nodes.addAll(Arrays.asList(isd));
    }
    
    @Override
    public ISecureData[] getAll() {
        return data.nodes.toArray(new ISecureData[data.nodes.size()]);
    }

    @Override
    public void add(SecureData sde) {
        data.nodes.add(sde);
    }

    @Override
    public void delete(SecureData sde) {
        data.nodes.remove(sde);
    }

    @Override
    public void saveAll(IStore isf, CryptInfoBundle bundle) throws IOException, CryptoException {
        if (bundle == null || bundle.getPassword() == null || bundle.getSalt() == null) {
            throw new NullPointerException("Bundle, password or salt must not be null values.");
        }

        String myData = this.data.toJson();
        IOContainer container = new IOContainer();
        container.instance.setData(myData, bundle);
        container.instance.setDBVersion(DatabaseVersion.DBv2);
        container.instance.setEncryptionVersion(EncryptionVersion.AESv1);
        container.instance.setChecksum(generateChecksum(bundle));
        container.instance.setSalt(bundle.getSalt());

//        try {
        if( Application.isDataVerificationSignatureValid() ) {
            isf.shouldOverwrite(true);
            isf.store(container.toJson());
        } else {
            throw new IOException("Application Data Verification failed.");
        }
//        } catch (Exception ex) {
//            Logger.getLogger(SecureDataManager.class.getName()).log(Level.SEVERE, null, ex);
//            throw new IOException(ex.getMessage());
//        }

    }

    @Override
    public void saveAll(IStore isf) throws IOException, CryptoException {
        saveAll(isf, null);
    }

    @Override
    public void loadAll(IFetch isf, CryptInfoBundle bundle) throws IOException, CryptoException {
        if (bundle == null || bundle.getPassword() == null ) {
            throw new NullPointerException("Bundle, password or salt must not be null values.");
        }

        String ldata = null;
        IOContainer container = new IOContainer();

        try {
            ldata = isf.fetch();
        } catch (Exception ex) {
            //Logger.getLogger(SecureDataManager.class.getName()).log(Level.SEVERE, null, ex);
            Logger.getLogger(SecureDataManager.class.getName()).log(Level.INFO, "There was a problem fetching the data.");
        }

        //The Salt is set inside of this method.
        container.fromJson(ldata, bundle);
        if (  bundle.getSalt() == null ) {
            throw new NullPointerException("Bundle, password or salt must not be null values.");
        }
        //Now that the json is loaded, let's check to see if the password is valid
        if (false == container.instance.getChecksum().equals(generateChecksum(bundle))) {
            throw new CryptoException("Invalid password!");
        }

        try {
            data.fromJson(container.instance.getData(bundle));
        } catch (JsonSyntaxException ex) {
            //Okay, if we got here, it means the JSON is messed up.  This probably
            //indicates that it isn't JSON at all, but still encrypted.  Most likely,
            //This is because the user entered the wrong password.
            Logger.getLogger(SecureDataManager.class.getName()).log(Level.INFO, "Unable to parse the JSON. Probably a bad password?");
            throw new CryptoException("Invalid password");
        }

    }

    @Override
    public void decryptEach(CryptInfoBundle bundle) {
        for (SecureData d : data.nodes) {
            d.decrypt(bundle);
        }
    }

    @Override
    public void encryptEach(CryptInfoBundle bundle) {
        for (SecureData d : data.nodes) {
            d.encrypt(bundle);
        }
    }

    @Override
    public void loadAll(IFetch isf) throws IOException, CryptoException {
        loadAll(isf, null);
    }

    @Override
    public void printAll() {
        System.out.println(this.data.toJson());
    }

    //TODO: Move this to a utils class someplace
    private String generateChecksum(CryptInfoBundle bundle) {

        String pwHex = DigestUtils.shaHex(bundle.getPassword());
        return pwHex;
    }

    private class Data {

        public List<SecureData> nodes;

        public Data() {
            nodes = new ArrayList<>();
        }

        public String toJson() {
            Gson gson = new Gson();
            return gson.toJson(nodes);
        }

        public void fromJson(String json) {
            Gson gson = new Gson();
            Type secureDataType = new TypeToken<List<SecureData>>() {
            }.getType();
            nodes = gson.fromJson(json, secureDataType);
        }
    }
}
